﻿using log4net;
using MimeKit;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;

namespace ScanDispatcher
{
    public class SmtpHandler
    {

        const string SMTPOK = "250 OK";
        const string SMTPPROTOERR = "500 Protocol Error";
        const string SMTPCLOSE = "221 closing channel";

        public NetworkStream Stream { get; set; }
        static ILog Log = LogManager.GetLogger("ScanDispatcher");

        public enum SmtpStates
        {
            Init,
            Envelope,
            Data,
            Close,
            Closed
        }

        public SmtpStates CurrentState = SmtpStates.Init;

        private byte[] buffer = new byte[8192];
        public void ProcessMessage()
        {

            //One big Try/Catch because this can well be considered an atomic transaction. 
            try
            {
                Log.Debug("Starting to receive new Message");

                //Simple, Straightforward SMTP handling. Absolutely insecure, use only in a very trusted environment. 
                var writer = new StreamWriter(Stream, Encoding.ASCII);
                var reader = new StreamReader(Stream, Encoding.ASCII);
                var message = new Message();

                writer.AutoFlush = true;

                while (CurrentState != SmtpStates.Closed)
                {

                    switch (CurrentState)
                    {
                        case SmtpStates.Init:
                            SmtpHelo(writer, reader);
                            break;
                        case SmtpStates.Envelope:
                            SmtpEnvelopeInformation(writer, reader, message);
                            break;
                        case SmtpStates.Data:
                            SmtpData(writer, message);
                            break;
                        case SmtpStates.Close:
                            SmtpClose(reader, writer);
                            break;
                        default:
                            break;
                    }
                }

                Log.Debug("Finished receiving smtp data, start processing attachments");

                var path = GetOutputPath(message);

                foreach (var file in message.PayLoad.Attachments)
                {

                    using (var output = File.Open(path + "\\" + file.FileName, FileMode.CreateNew))
                    {

                        Log.Debug("Writing File: " + path + "\\" + file.FileName);
                        file.ContentObject.DecodeTo(output);
                    }
                }



            }
            catch (Exception ex)
            {
                Log.Error("Error receiving message", ex);
            }
            finally
            {
                if (Stream != null) Stream.Close();
            }

        }

        private void SmtpClose(StreamReader reader, StreamWriter writer)
        {

            var QuitLine = reader.ReadLine();
            writer.WriteLine(SMTPCLOSE);

            writer.Close();
            reader.Close();
            Stream.Close();
            Stream = null;
            CurrentState = SmtpStates.Closed;
        }

        private void SmtpData(StreamWriter writer, Message message)
        {


            //Receive actual data. 
            var memstream = new MemoryStream();
            int bytes = 0;
            while (true)
            {

                bytes = Stream.Read(buffer, 0, buffer.Length);
                memstream.Write(buffer, 0, bytes);

                if (Encoding.ASCII.GetString(buffer).Contains("\r\n.\r\n")) break;
            }
            memstream.Position = 0;
            message.PayLoad = MimeKit.MimeMessage.Load(memstream);

            writer.WriteLine(SMTPOK);
            CurrentState = SmtpStates.Close;
        }


        private static string GetOutputPath(Message message)
        {
            String name = "";
            var recipient = message.PayLoad.To.First() as MailboxAddress;
            if (recipient == null)
            {
                name = "info";
            }
            else
            {
                name = recipient.Address.Split("@".ToCharArray()).First();
            }

            Log.Debug("Recipient: " + recipient.Address);
            String DateFolder = ScanDispatcher.Properties.Settings.Default.DateSubfolders ? "\\" + DateTime.Now.ToString("yyyy-MM-dd") : "";

            var path = ScanDispatcher.Properties.Settings.Default.BasePath + "\\" + name + DateFolder;
            if (!Directory.Exists(path))
            {
                Directory.CreateDirectory(path);
            }
            return path;
        }


        private void SmtpEnvelopeInformation(StreamWriter writer, StreamReader reader, Message message)
        {

            try
            {

                var inputLine = reader.ReadLine();
                if (inputLine.StartsWith("RCPT TO"))
                {
                    message.Recipients.Add(inputLine.Split(':').Last());
                    writer.WriteLine(SMTPOK);
                    return;
                }
                else if (inputLine.StartsWith("MAIL FROM"))
                {
                    // We don't handle "from" yet
                    writer.WriteLine(SMTPOK);
                    return;
                }
                else if (inputLine.StartsWith("DATA"))
                {
                    CurrentState = SmtpStates.Data;
                    writer.WriteLine("354 start mail input");
                    return;
                }
            }
            catch (IOException ex)
            {
                Log.Error("Error in Headers", ex);
                CurrentState = SmtpStates.Closed;
            }
        }


        private void SmtpFrom(StreamWriter writer, StreamReader reader)
        {
            //Ignore Sender. Accept any mail. 
            var senderAddress = reader.ReadLine();
            if (!senderAddress.StartsWith("MAIL FROM:"))
            {
                writer.WriteLine(SMTPPROTOERR);
                writer.WriteLine(SMTPCLOSE);
                Stream.Close();
                CurrentState = SmtpStates.Closed;
            }
            writer.WriteLine(SMTPOK);
        }

        private void SmtpHelo(StreamWriter writer, StreamReader reader)
        {
            try
            {
                // Initial HELO

                writer.WriteLine("220 service ready");

                var clientGreeting = reader.ReadLine();
                if (!(clientGreeting.StartsWith("HELO") || clientGreeting.StartsWith("EHLO")))
                {
                    writer.WriteLine(SMTPPROTOERR);
                    writer.WriteLine(SMTPCLOSE);
                    Stream.Close();
                    CurrentState = SmtpStates.Closed;
                }
                writer.WriteLine(SMTPOK);
                CurrentState = SmtpStates.Envelope;
            }
            catch (IOException ex)
            {
                Log.Error("Error in HELO", ex);
                CurrentState = SmtpStates.Closed;
            }
        }
    }
}
